;***************************************************************************************************************
; the FAT32 filesystem bootsector
; parses FAT32 file system to find file "bootme.sys" and loads to 0x0000:0x9000
; execution is then passed to bootme.sys

; NOTE: this only works on partitions up to 8GB in size and bootme.sys is limited to 4096 bytes.

[BITS 16]
[ORG 0x7c00]

START:
	; jump over BPB
	jmp	BPB_END

;***************************************************************************************************************
; the Bios Parameter Block
; make sure it is exactly 3 bytes in
times	3 - ($ - $$) db 0

BPB_OEMName			db "mkdosfs",0
BPB_BytesPerSector		dw 0x0200
BPB_SectorsPerCluster		db 0x08
BPB_ReservedSectors		dw 0x0020
BPB_NumberOfFATs		db 0x02
BPB_RootEntryCount		dw 0	; 0 for FAT32
BPB_TotalSectors16		dw 0	; 0 for FAT32
BPB_MediaDescriptor		db 0xf8	; 0xf8 for fixed disks
BPB_FATSize16			dw 0	; 0 for FAT32
BPB_SectorsPerTrack		dw 0x003f
BPB_NumberOfHeads		dw 0x00ff
BPB_HiddenSectors		dd 0
BPB_TotalSectors32		dd 0x001fddc2
BPB_FATSize32			dd 0x000007f4
BPB_ExtFlags			dw 0
BPB_VersionNumber		dw 0
BPB_RootCluster			dd 0x00000002
BPB_FSInfo			dw 0x0001
BPB_BootSectorBackUp		dw 0x0006
BPB_Reserved0			dd 0
BPB_Reserved1			dd 0
BPB_Reserved2			dd 0
BPB_DriveNumber			db 0
BS_Reserved3			db 0
BS_BootSig			db 0
BS_VolumeID			dd 0
BS_VolumeLabel			db "Test       "
BS_FileSystemType		db "FAT32   "

times 90 - ($ - $$) db 0

;***************************************************************************************************************

BPB_END:
	; save si (points to partition entry)
	push	si
	
	; save drive number
	mov	[BPB_DriveNumber], dl
	
	; clear partition offset value
	xor	eax, eax
	mov	[0x7e10], eax
	
	; check if disk is partitioned
	; byte at si should be 0x80 (or 0x81 if using 48 bit)
	mov	al, [si]
	and	al, 0x80
	jz	NOT_PARTITIONED

	; drive is partitioned, get offset from partition table
	mov	eax, [si + 8]
	mov	[0x7e10], eax

NOT_PARTITIONED:
	; check file system is FAT32 (could be FAT12 or FAT16)
	cmp	WORD [BPB_RootEntryCount], 0
	jne	ERROR

CALCULATE_SOME_VALUES:
	; calculate some required values
	; calculate start sector of data area (cluster 2)
	; c2 start = (no. of FATS * FAT size) + reserved sectors
	mov	eax, [BPB_FATSize32]
	movzx	ecx, BYTE [BPB_NumberOfFATs]
	mul	ecx
	movzx	ecx, WORD [BPB_ReservedSectors]
	add	eax, ecx;[BPB_ReservedSectors]	; this assumes that calculations do not go over 64k
	mov	[0x7e00], eax			; save start sector of data area
	
LOAD_ROOT_DIR_CLUSTER:
	; load the cluster containing the root dir
	mov	eax, [BPB_RootCluster]
	mov	si, 0x8000
	call	LoadCluster
	
FIND_FILE:
	; find the file called "bootme.sys"
	; file name should be stored as "BOOTME  SYS" in FS
	; setup for compare entry loop
	; set dl to counter (128 entries) and cx to base address of entries (0x8000)
	mov	dl, 128
	mov	cx, 0x8000

.entry_loop:
	; check counter for 0
	or	dl, dl
	jz	ERROR

	; decrease counter
	dec	dl
	
	; set di to point to entry string
	mov	di, cx

	; set si to point to string to test against
	mov	si, dataFileName
	
.string_loop:
	; compare bytes
	mov	al, [si]
	mov	ah, [di]
	cmp	al, ah
	jne	.not_equal
	
	; bytes are equal
	; increase pointers
	inc	di
	inc	si
	
	; check for end of test string
	mov	al, [si]
	or	al, al
	jz	LOAD_FILE
	jmp	.string_loop

.not_equal:
	; bytes weren't equal
	; increase cx to point to next entry
	add	cx, 32
	
	; try again
	jmp	.entry_loop

LOAD_FILE:
	; strings are equal
	; match found, entry pointed to by cx
	; get cluster number low 16 bits
	add	cx, 26
	
	; get the cluster number
	mov	si, cx
	movzx	eax, WORD [si]
	
	; load that cluster to 0x9000
	mov	si, 0x9000
	call	LoadCluster
	
.finish:	
	; restore pointer to partition table
	pop	si
	
	; save drive number in dl
	mov	dl, [BPB_DriveNumber]
	
	; jump to code
	jmp	0x0000:0x9000
	
ERROR:
	; go here on error
	mov	si, msgErrorNoFile
	call	PrintString

	; wait for keyboard input
	mov	ah, 0x10
	int	0x16
	
	; reboot
	int	0x19

;***************************************************************************************************************
; functions

;*******************************
; print a 32 bit number (unsigned)
; eax = number
; ecx = base (2 - 16)

PrintNumber:
	; setup a null-terminated array on the stack
	; setup so that default array is "0",0 (char for 0, with nullterminator)
	; to account for values of 0 being printed
	; point si to a word before the end
	push	WORD 0x0030
	mov	si, sp
	sub	sp, 12
	
	; check value is not zero
	or	eax, eax
	jz	.loop

	; number is not zero, increase si to remove "0" char
	;inc	si
	
.loop:
	; get the next character's value
	xor	edx, edx
	div	ecx
	
	; check if finished (quotient = 0, remainder = 0)
	or	eax, eax	; quotient
	jnz	.not_finished
	or	edx, edx	; remainder
	jnz	.not_finished
	jmp	.print
	
.not_finished:
	; decrease si to point to previous byte - create characters in reverse
	dec	si
	
	; check if value is larger than 10
	cmp	dx, 10
	jl	.smaller_10
	
	; value is larger than 10, so add offset to capital letters from
	; numbers - 10 (ASCII code)
	add	dx, 0x07 
	
.smaller_10:
	; add base value for ASCII numbers 0 - 9
	add	dx, 0x30

	; save the value
	mov	[si], dl

	; loop
	jmp	.loop

.print:
	; print the generated string
	call	PrintString

.finish:
	; restore stack + return
	add	sp, 14	
	ret

;*******************************
; print a string
; si = pointer to string to print

PrintString:
	; setup registers for video out mode
	mov	ah, 0x0e
	mov	bh, 0x00
	mov	bl, 0x07
	
.loop:	
	; get char
	lodsb		; same as "mov	al, [si] + inc	si"

	; check for nullchar
	or	al, al
	jz	.finish
	
	; if not null, print it & repeat
	int	0x10
	jmp	.loop
	
.finish:
	ret

;*******************************
; load a cluster
; eax = cluster to load
; ds:si = target start address

LoadCluster:
	; calculate start LBA
	; first sector of cluster = ((N - 2) * sectors_per_cluster) + first data sector
	sub	eax, 2					; N - 2
	movzx	ecx, BYTE [BPB_SectorsPerCluster]	; load sectors per cluster into cl
	mov	bx, cx					; save sectors per cluster into bx
	mul	ecx					; multiply
	add	eax, [0x7e00]				; where the first data sector value is stored	

	; call the drive read function
	call	ReadDrive

.finish:
	ret

;*******************************
; new drive read function
; eax = start lba
; bx = count
; ds:si = target start address

msgDriveError:		db	13,10,"Read error occured",0

ReadDrive:
	; fetch the drive number
	mov	dl, [BPB_DriveNumber]

	; add sector offset for partition start
	add	eax, [0x7e10]

	; put DAP values onto the stack
	; must be done in REVERSE order
	push	DWORD 0		; DAP sector start high 32 bits
	push	eax		; DAP sector start low 32 bits
	push	ds		; DAP segment
	push	si		; DAP offset
	push	bx		; DAP sector count
	push	WORD 0x0010	; DAP size + unused
	
	; save the pointer of the top of the stack
	push	sp

	; setup for the loop
	mov	di, 10		; 10 attempts

.loop:
	; decrease loop counter
	dec	di

	; check if counter = 0
	or	di, di
	jz	.fail

	; attempt to reset the drive
	; setup for drive reset
	mov	ah, 0

	; call the interrupt
	int	0x13

	; if reset failed, try again
	jc	.loop

	; drive reset was success, now try the read
	; setup for extended read
	pop	si		; get the pointer to the DAP
	push	ds		; save ds
	mov	ax, ss		; get the segment of the DAP
	mov	ds, ax
	mov	ah, 0x42	; extended read

	; call the interrrupt
	int	0x13

	; restore ds
	pop	ds
	
	; if read failed, try again
	jc	.loop

	; restore the stack
	add	sp, 16

.finish:
	; finished - clear carry flag and return
	clc
	ret
	
.fail:
	; failed - print error message and halt
	mov	si, msgDriveError
	call	PrintString
	cli
	hlt

;***************************************************************************************************************
; data

dataFileName		db "BOOTME  SYS",0	; the name of the file to load
msgErrorNoFile		db 13,10,"Error no file",13,10,"Press any key to reboot",0

;***************************************************************************************************************
; bootsig at end
times 	510 - ($ - $$) db 0
dw 	0xAA55
